package com.sfx.concurrent.demo.concurrent.SingletonTest;

/**
 * 双重校验锁 DCL  -> 单例模式
 */
public class Singleton4 {

    private Singleton4() {
        System.out.println("Singleton4()");
    }
    // 问题1：解释为什么要加 volatile ?
    /**
     * 主要解决有序性问题
     * 比如会发生指令重排序,创建对象的时候是先赋值操作,在调用构造方法
     * 当一个线程创建对象的时候是先赋值操作,在调用构造方法,另外一个线程执行也来了对比
     * INSTANCE!=null 所以直接会把INSTANCE返回,就会导致返回一个还没有初始化的对象->导致空指针异常(最终生成不完整的对象)
     * 加了volatile之后构造方法不会再赋值之后
     * 加了volatile修饰时候保证本行代码初始化方法不会跑到赋值语句的后面
     */
    private static volatile Singleton4 INSTANCE;
    // 问题2：对比实现3, 说出这样做的意义
    //只有第一次调用getInstance()方法才会加锁
    //后续在调用getInstance()不需要加锁
    public static Singleton4 getInstance() {
        //首次使用才会同步
        if(INSTANCE ==null) {
            synchronized (Singleton4.class) { //只有第一次才对其加锁
                // 问题3：为什么还要在这里加为空判断, 之前不是判断过了吗
                //主要是为了防止首次去创建单例对象Singleton4的时候不被重复创建(出现并发安全问题)
                //如果是下面这样的
                /**
                 *  //if(INSTANCE == null) {
                 *       INSTANCE = new Singleton4();
                 *  //}
                 *  在初次创建单例是,第一个线程执行完了创建出INSTANCE对象了,第二个线程就可以加锁了
                 *  如果没有INSTANCE == null这个判断的话,那么就会又创建出一个实例对象了
                 */
                if(INSTANCE == null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }
}
